package com.ufgov.smartclient.component.table;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.LayoutManager2;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;

/**
 * <p>Title: com.ufgov.smartclient.component</p>
 * <p>Description: 带滚动条的右键菜单布局管理器</p>
 * <p>Copyright: Copyright 2009 ufgov, Inc.</p>
 * <p>Company: ufgov</p>
 * <p>创建时间: 2009-4-16</p>
 * @author 刘永伟(manlge)
 * @version 1.0
 */

/*
 * 实现思想：
 *      当 JPopupMenu使用ScrollableMenuLayout时，会调用addLayoutComponent方法添加组件
 *      可以通过该方法添加一个JScrollBar到JPopupMenu，在JPopupMenu进行布局时，
 *      计算是否超出了显示范围，如果超出，添加JScrollBar到右边，JScrollBar在滚动时
 *      将所有组件的y坐标上移JScrollBar.getValue()个象素，并对JPopupMenu重新绘制,
 *      
 * 菜单大小控制
 *      JPopupMenu会调用preferredLayoutSize来取弹出菜单的大小，在此方法中可以计算出
 *      所有组件的高度（scrollBar除外），当高度超过半屏时，初始滚动条为显示状态，并使
 *      宽度增加20，用来显示滚动条
 *      
 * 布局控制
 *      在布局时，其他组件按添加的顺序显示，宽度使用最大组件的宽度，滚动条则占右边20
 *      像素，高度与弹出菜单相同
 */
public class ScrollableMenuLayout implements LayoutManager2, AdjustmentListener {

  /**
   * 滚动条的宽度
   */
  private static final int SCROLLBAR_WIDTH = 20;

  /**
   * 滚动条，用来滚动菜单项
   */
  private JScrollBar scrollBar = new JScrollBar();

  /**
   * 是否显示滚动条，如果在菜单数量较少时不显示，超过半屏时显示
   */
  private boolean showScrollBar = false;

  /**
   * 最大的组件宽度
   */
  private int maxCompWidth = 0;

  /**
   * 除滚动条以外的其他需要布局的组件
   */
  private List<Component> items = new ArrayList<Component>();

  private JPopupMenu popupMenu = null;

  public ScrollableMenuLayout() {
    scrollBar.setMaximum(0);
    scrollBar.setMinimum(0);
    scrollBar.setValue(0);
    scrollBar.addAdjustmentListener(this);
  }

  @Override
  public void addLayoutComponent(String name, Component comp) {

  }

  @Override
  public void layoutContainer(Container parent) {
    this.popupMenu = (JPopupMenu) parent;
    Rectangle rect = parent.getBounds(); //整个菜单的范围
    int y = 0; //行坐标
    int tHeight = 0; //总的高度

    y -= scrollBar.getValue(); //根据滚动条设置偏移量

    /**
     * 当第一个组件显示时，如果y的值为0，则会遮挡上面的菜单线条，因此加1可避免该情况出现，
     * 当显示最后一个组件时，底部也会出现同样问题，通过-1可以解决该问题
     */
    if (y == 0) {
      y = 1;
    } else {
      y -= 1;
    }

    for (int i = 0; i < items.size(); i++) {
      Component component = items.get(i);
      int h = component.getPreferredSize().height;
      component.setBounds(1, y, maxCompWidth, h);
      y += h;
      tHeight += h;
    }
    /**
     * 当滚动条显示时，已经提供了菜单的height来显示组件，这个高度不应纳入滚动条的value
     */
    tHeight -= rect.height;
    scrollBar.setMaximum(tHeight);
    scrollBar.setMinimum(0);
    /**
     * 设置滚动单位，第次滚动一个组件的平均高度
     */
    scrollBar.setUnitIncrement(items.size() == 0 ? 0 : tHeight / items.size());

    /**
     * 设置滚动条
     */
    scrollBar.setBounds(rect.width - SCROLLBAR_WIDTH, 0, SCROLLBAR_WIDTH,
      rect.height);

    scrollBar.setVisible(showScrollBar);
  }

  @Override
  public Dimension minimumLayoutSize(Container parent) {
    return preferredLayoutSize(parent);
  }

  @Override
  public Dimension preferredLayoutSize(Container parent) {
    this.popupMenu = (JPopupMenu) parent;
    int totalHeight = 0;
    for (int i = 0; i < items.size(); i++) {
      Component component = items.get(i);
      Dimension preferredSize = component.getPreferredSize();
      int h = preferredSize.height;
      maxCompWidth = Math.max(maxCompWidth, preferredSize.width);
      totalHeight += h;
    }
    Toolkit toolKit = Toolkit.getDefaultToolkit();
    /**
     * 弹出菜单的高度不超过半屏
     */
    int minHeight = Math.min(totalHeight, toolKit.getScreenSize().height / 2);

    if (minHeight < totalHeight) { //实际高度 > 显示高度时显示滚动条
      showScrollBar = true;
    } else {
      showScrollBar = false;
    }
    /**
     * +2 是为边线预留空间
     */
    return new Dimension((showScrollBar ? maxCompWidth + SCROLLBAR_WIDTH
      : maxCompWidth) + 2, minHeight + 2);
  }

  @Override
  public void removeLayoutComponent(Component comp) {
    if (comp != scrollBar)
      items.remove(comp);
  }

  @Override
  public void addLayoutComponent(Component comp, Object constraints) {
    if (comp != scrollBar) {
      items.add(comp);
    }

    Container parent = comp.getParent();
    if (scrollBar.getParent() != parent) {
      parent.add(scrollBar);
    }
  }

  @Override
  public float getLayoutAlignmentX(Container target) {
    return 0;
  }

  @Override
  public float getLayoutAlignmentY(Container target) {
    return 0;
  }

  @Override
  public void invalidateLayout(Container target) {
  }

  @Override
  public Dimension maximumLayoutSize(Container target) {
    return preferredLayoutSize(target);
  }

  @Override
  public void adjustmentValueChanged(AdjustmentEvent e) {
    if (popupMenu != null) {
      popupMenu.revalidate();
      popupMenu.repaint();
    }
  }

}
